\chapter{计算机启动} \label{CHboot}

每一个计算机软件都是由一系列的可执行文件组成的，可执行文件的内容是可以被机器识别的二进制指令和数据。一般可执行文件的运行是在操作系统的照看下加载进内存并运行的，由操作系统给它分配资源和处理器时间，并确定它的执行方式。操作系统也是由可执行文件组成的，但是操作系统的启动方式和一般应用软件是不同的，这也就是它叫做“操作系统”的原因~\smiley。

没有操作系统的机器，一般情况下被我们称为“裸机”，意思就是只有硬件，什么都干不了。但是一个机器怎么知道自己是不是裸机呢？它总要有方式去判断机器上安装没有安装操作系统吧。下面我们就简单介绍一下计算机启动的过程：

\section{计算机启动过程} \label{CHboot_boot}

\textbf{计算机启动过程}一般是指计算机从点亮到加载操作系统的一个过程。对于~IBM~兼容机（个人电脑）来讲，这个过程大致是这样的：

\begin{enumerate}
\item{\textbf{加电}} 电源开关被按下时，机器就开始供电，主板的控制芯片组会向~CPU~（Central Processing Unit，中央处理器）发出并保持一个~RESET~（重置）信号，让~CPU~恢复到初始状态。当芯片组检测到电源已经开始稳定供电时就会撤去~RESET~信号（松开台式机的重启键是一样的效果），这时~CPU~就从~\code{0xffff0}~处开始执行指令。这个地址在系统~BIOS~（Basic Input/Output System，基本输入输出系统）的地址范围内，大部分系统~BIOS~厂商放在这里的都只是一条跳转指令，跳到系统~BIOS~真正的启动代码处。

\item{\textbf{自检}} 系统~BIOS~的启动代码首先要做的事情就是进行~POST~（Power-On Self Test，加电后自检），POST~的主要任务是检测系统中一些关键设备是否存在和能否正常工作，例如内存和显卡等。由于~POST~是最早进行的检测过程，此时显卡还没有初始化，如果系统~BIOS~在~POST~的过程中发现了一些致命错误，例如没有找到内存或者内存有问题（此时只会检查~640K~常规内存），那么系统~BIOS~就会直接控制喇叭发声来报告错误，声音的长短和次数代表了错误的类型。

\item{\textbf{初始化设备}} 接下来系统~BIOS~将查找显卡的BIOS，存放显卡~BIOS~的~ROM~芯片的起始地址通常设在~\code{0xC0000}~处，系统~BIOS~在这个地方找到显卡 ~BIOS~之后就调用它的初始化代码，由显卡~BIOS~来初始化显卡，此时多数显卡都会在屏幕上显示出一些初始化信息，介绍生产厂商、图形芯片类型等内容。系统~BIOS~接着会查找其它设备的~BIOS~程序，找到之后同样要调用这些~BIOS~内部的初始化代码来初始化相关的设备。

\item{\textbf{测试设备}} 查找完所有其它设备的~BIOS~之后，系统~BIOS~将显示出它自己的启动画面，其中包括有系统~BIOS~的类型、序列号和版本号等内容。接着系统~BIOS~将检测和显示~CPU~的类型和工作频率，然后开始测试所有的~RAM~（Random Access Memory，随机访问存储器），并同时在屏幕上显示内存测试的进度。内存测试通过之后，系统~BIOS~将开始检测系统中安装的一些标准硬件设备，包括硬盘、光驱、串口、并口、软驱等，另外绝大多数较新版本的系统~BIOS~在这一过程中还要自动检测和设置内存的定时参数、硬盘参数和访问模式等。标准设备检测完毕后，系统~BIOS~内部的支持即插即用的代码将开始检测和配置系统中安装的即插即用设备，每找到一个设备之后，系统~BIOS~都会在屏幕上显示出设备的名称和型号等信息，同时为该设备分配中断（INT）、DMA~（Direct Memory Access，直接存储器存取）通道和~I/O~（Input/Output，输入输出）端口等资源。

\item{\textbf{更新~ESCD}} 所有硬件都检测配置完毕后，多数系统~BIOS~会重新清屏并在屏幕上方显示出一个表格，其中概略地列出了系统中安装的各种标准硬件设备，以及它们使用的资源和一些相关工作参数。接下来系统~BIOS~将更新~ESCD~（Extended System Configuration Data，扩展系统配置数据）。~ESCD~是系统~BIOS~用来与操作系统交换硬件配置信息的一种手段，这些数据被存放在~CMOS~（Complementary Metal Oxide Semiconductor，互补金属氧化物半导体）之中。

\item{\textbf{启动操作系统}} \label{bootsec-1} ~ESCD~更新完毕后，系统~BIOS~的启动代码将进行它的最后一项工作，即根据用户指定的启动顺序从软盘、硬盘或光驱启动操作系统。以~Windows XP~为例，系统~BIOS~将启动盘（一般是主硬盘）的第一个扇区（Boot Sector，引导扇区）读入到内存的~\code{0x7c00}~处，并检查~\code{0x7dfe}~地址的内存，如果其内容是~\code{0xaa55}，跳转到~\code{0x7c00}~处执行~MBR~（Master Boot Record，主引导记录），MBR~接着从分区表（Partition Table）中找到第一个活动分区（Active Partition，一般是~C~盘分区），然后按照类似方式读取并执行这个活动分区的引导扇区（Partition Boot Sector），而引导扇区将负责读取并执行~NTLDR~（NT LoaDeR，Windows NT~的加载程序），然后主动权就移交给了~Windows~。
\end{enumerate}

从以上介绍中我们可以看到，在第~\ref{bootsec-1}~步之前，电脑的启动过程完全依仗于系统~BIOS~，这个程序一般是厂商写就固化在主板上的。我们所需要做的，就是第~\ref{bootsec-1}~步之后的内容，即：
\begin{quote}
\textbf{如何写一个操作系统并把它加载到内存?}
\end{quote}

\section{磁盘抽象物理结构}\label{disk_structure}

由于操作系统的启动涉及到硬件地址写入和磁盘文件寻找，为了更好理解内存地址和文件存储的相关知识，我们先来了解一下磁盘的结构。

\subsection{硬盘}

\FIGFIX{硬盘}{hd1}{0.8\textwidth}

图~\ref{hd1}~所示就是硬盘（如非特指，我们这里的“硬盘”一般指代磁介质非固态硬盘）的外观图。其中左边是硬盘盒拆开后盘片、磁头和内部机械结构的透视图，右边是普通台式机硬盘的外观图。现在的硬盘容量较以前已经有大幅度增加，一般笔记本电脑硬盘容量已经在~120G~以上，台式机硬盘容量一般也达到了~160G~大小。一般情况下，硬盘都是由坚硬金属材料（或者玻璃等）制成的涂以磁性介质的盘片构成的，一般有层叠的多片，每个盘片都有两个面，两面都可以记录信息。

\FIGFIX{硬盘的抽象物理结构}{hd2}{13.04cm}

图~\ref{hd2}~为硬盘的抽象物理结构，需要注意的是这并不是硬盘真正的物理构造，所以这里我们称其为“抽象”物理结构。因此我们下面讨论的也不是真正的硬盘技术实现，仅仅就硬盘（以及软盘等类似磁介质存储器）存储结构以程序员易于理解的角度进行简单的介绍。

如图~\ref{hd2}~所示，硬盘是由很多盘片组成的，那些上下有分割的圆盘就表示一个个盘片。每个盘片被分成许多扇形的区域，每个区域叫一个扇区，通常每个扇区存储~512~字节（~FAT~文件格式），盘片表面上以盘片中心为圆心，不同半径的同心圆称为磁道。硬盘中，不同盘片相同半径的磁道所组成的圆柱称为柱面。磁道与柱面都是表示不同半径的圆，在许多场合，磁道和柱面可以互换使用。每个磁盘有两个面，每个面都有一个磁头，习惯用磁头号来区分。扇区，磁道（或柱面）和磁头数构成了硬盘结构的基本参数，使用这些参数可以得到硬盘的容量，其计算公式为：
\begin{quote}
存储容量~＝~磁头数~×~磁道（柱面）数~×~每磁道扇区数~×~每扇区字节数
\end{quote}

\BOXED{0.9\textwidth}{
\textbf{要点：}
\begin{itemize} 
\item 硬盘有数个盘片，每盘片两个面，每面一个磁头。
\item 盘片被划分为多个扇形区域即扇区。
\item 同一盘片不同半径的同心圆为磁道。
\item 不同盘片相同半径构成的圆柱面即柱面。
\item 公式：存储容量~＝~磁头数~×~磁道（柱面）数~×~每道扇区数~×~每扇区字节数。
\item 信息记录可表示为：××磁道（柱面），××磁头，××扇区。
\end{itemize}
}

\subsection{软盘}
由于我们在本书中主要使用软盘作为系统启动盘，所以下面对应于硬盘介绍一下软盘的相关知识。

\FIG{软盘}{floppy1}{0.8\textwidth}

现在通常能看到的软盘主要是~3.5~英寸软盘，3.5~英寸指的是其内部磁介质盘片的直径。从存储结构上来讲，软盘与硬盘的主要不同就是软盘只有一个盘片且其存储密度较低。

由于软盘只有一个盘片，两个面，所以~3.5~英寸软盘的容量可以根据上一小节的公式算出：

2(磁头)~×~80(磁道)~×~18(扇区)~×~512 bytes(扇区的大小)~=~2880 x 512 bytes = 1440 KB = 1.44MB

在这里需要引起我们特别注意的就是第~0~号磁头（面），第~0~号磁道的第~0~号扇区，这里是一切的开始。

\subsection{启动扇区}

软盘是没有所谓的~MBR~的，因为软盘容量较小，没有所谓的分区，一张软盘就显示为一个逻辑磁盘。当我们使用软盘启动电脑的时候，系统从软盘中首先读取的就是第一个扇区，即前面所说的第~0~面，第~0~号磁道的第~0~号扇区，如果这个扇区的最后两个字节是~\code{0xaa55}~，这里就简单叫做启动扇区（Boot Sector）。所以我们首先要做的就是：在启动扇区的开始填入需要被执行的机器指令；在启动扇区的最后两个字节中填入~\code{0xaa55}，这样这张软盘就成为了一张可启动盘。

\BOXED{0.9\textwidth}{
\danger启动扇区最后两个字节的内容为~\code{0xaa55}~，这种说法是正确的——当且仅当表~\ref{bootsec_BPB}~中的~BPB\_BytesPerSec~（每扇区字节数）的值为~512~。如果~BPB\_BytesPerSec~的值大于~512~，~\code{0xaa55}~的位置不会变化，但已经不是启动扇区最后两个字节了。\enddanger
}

整个过程如图~\ref{bootsector1}~所示：

\FIG{启动扇区加载示意图}{bootsector1}{0.75\textwidth}

需要注意的是，软盘的启动扇区并不像一个文件一样，可以直接读取，写入启动扇区的过程是需要一些技巧的，下面我们将讨论如何去实现。

\section{使用虚拟机}

在实现一个简单的操作系统时，我们是不可能拿一台真正的机器做实验的，一是很少有人有这个条件，还有就是那样做比较麻烦。所以我们使用虚拟机来模拟一台真实的电脑，这样我们就能直接用虚拟机加载软盘镜像来启动了，而制作软盘镜像显然要比写一张真实的软盘简单许多。

在~Linux~下有很多虚拟机软件，我们选择~VirtualBox~和~Bochs~作为主要的实现平台，我们用~VirtualBox~做~demo~，而~Bochs~主要用作调试。下面给出一些虚拟机设置的指导，其实用哪种虚拟机都没有关系，我们需要的只是虚拟机支持加载软盘镜像并能从软盘启动。

\subsection{VirtualBox}

~VirtualBox~是遵从~GPL~协议的开源软件，它的官方网站是~\url{http://www.virtualbox.org}~。~VirtualBox~的官方网站上提供针对很多~Linux~系统平台的二进制安装包，比如针对~Red Hat~系列（Fedora, RHEL）各种版本的~RPM~安装包，针对~Debian~系（Debian, Ubuntu）各种版本的~DEB~安装包，其中~Ubuntu
Linux~可以更方便地从~Ubuntu~软件仓库中直接下载安装：~\code{sudo apt-get install virtualbox}~。

安装好~VirtualBox~后，需要使用~\code{sudo adduser `whoami` vboxusers}~（某些系统中的添加用户命令可能是~useradd~）将自己添加到~VirtualBox~的用户组~vboxusers~中去；当然，也可以使用~GNOME~或者~KDE~的图形界面用户和组的管理工具来添加组用户，也可以直接编辑~/etc/group~文件，将自己的用户名添加到~vboxusers~对应行的最后，例如~\code{vboxusers:x:501:solrex}~，部分~Linux~可能需要注销后重新登录当前用户。

我们下面使用~CentOS~上安装的~VirtualBox~演示如何用它建立一个虚拟机。

第一次启动~VirtualBox~，会首先弹出一个~VirtualBox~个人使用协议~PUEL~的对话框（某些版本的~Linux~可能不会弹出）：\\
\FIGFIX{VirtualBox~个人使用协议}{vb_once_1}{0.7\textwidth}

阅读完协议后，将下拉条拉到最低可以激活最下方的同意按钮，点击之：\\
\FIGFIX{同意~VirtualBox~个人使用协议}{vb_once_2}{0.7\textwidth}

弹出的~VirtualBox~用户注册对话框，可忽视关闭之：\\
\FIGFIX{VirtualBox~用户注册对话框}{vb_once_3}{0.75\textwidth}

接下来我们就见到了~VirtualBox~主界面：\\
\FIGFIX{VirtualBox~主界面}{vb_main_1}{0.75\textwidth}

点击~New~按钮新建一个虚拟机：\\
\FIGFIX{新建一个虚拟机}{vb_new_1}{0.75\textwidth}

我们使用~solrex~作为虚拟机的名字，系统类型未知：\\
\FIGFIX{设置虚拟机名字和操作系统类型}{vb_new_2}{0.75\textwidth}

设置虚拟机的内存容量，这里随便设了~32M：\\
\FIGFIX{设置虚拟机内存容量}{vb_new_3}{0.75\textwidth}

设置虚拟机硬盘镜像：\\
\FIGFIX{设置虚拟机硬盘镜像}{vb_new_4}{0.75\textwidth}

如果没有硬盘镜像，需点“New”新建一块硬盘镜像：\\
\FIGFIX{新建一块虚拟硬盘}{vb_newhd_1}{0.75\textwidth}

点“Next”，设置虚拟硬盘镜像为可自动扩充大小：\\
\FIGFIX{设置虚拟硬盘类型}{vb_newhd_2}{0.75\textwidth}

这里将虚拟硬盘镜像的名字设置为“solrex”，并将容量设置为“32M”：\\
\FIGFIX{设置虚拟硬盘镜像名字和容量}{vb_newhd_3}{0.75\textwidth}

最后查看新建的虚拟硬盘信息，点击~Finish~确认新建硬盘镜像：\\
\FIGFIX{虚拟硬盘信息}{vb_newhd_4}{0.75\textwidth}

令虚拟机使用已建立的虚拟硬盘~solrex.vdi~：\\
\FIGFIX{使用新建的虚拟硬盘}{vb_new_5}{0.75\textwidth}

最后查看新建的虚拟机信息，点击~Finish~确认新建虚拟机：\\
\FIGFIX{虚拟机信息}{vb_new_6}{0.75\textwidth}

回到~VirtualBox~主界面，左侧列表中有新建立的虚拟机~solrex~：\\
\FIGFIX{回到~VirtualBox~主界面}{vb_main_2}{0.75\textwidth}

\subsection{Bochs}

\section{使用软盘镜像}

\subsection{制作软盘镜像}
前面我们说过，软盘的结构比较简单，所以我们选择使用软盘镜像来启动虚拟计算机。在~Linux~下制作一个软盘镜像很简单，只需要使用：
\begin{Command}
$ dd if=/dev/zero of=emptydisk.img bs=512 count=2880
\end{Command}
命令就可以在当前目录下生成一个名为~\code{emptydisk.img}~的空白软盘镜像，下面我们使用这个空白软盘镜像来启动虚拟机。

\BOXED{0.9\textwidth}{

~~~~\textbf{dd}~：转换和拷贝文件的工具。~dd~可以设置很多拷贝时候的参数，在本例中~if=FILE~选项代表从~FILE~中读取内容；~of=FILE~选项代表将导出输出到~FILE~；~bs=BYTES~代表每次读取和输出~BYTES~个字节；~count=BLOCKS~代表从输入文件中共读取~BLOCKS~个输入块。

~~~~而这里的~/dev/zero~则是一个~Linux~的特殊文件，读取这个文件可以得到持续的~0~。那么上面命令的意思就是以每块~512~字节共~2880~块全空的字符填入文件~emptydisk.img~中。我们注意到前面提及的软盘容量计算公式：

~~~~2(磁头)~×~80(磁道)~×~18(扇区)~×~512 bytes(扇区的大小)~=~2880 x 512 bytes = 1440 KB = 1.44MB

~~~~可以发现我们用上述命令得到的就是一张全空的未格式化的软盘镜像。
}

\subsection{用软盘镜像启动虚拟机}\label{CHboot_fboot}

在虚拟机主界面选中虚拟机后点~Settings~按钮，进入虚拟机的设置界面：\\
\FIGFIX{虚拟机设置界面}{vb_set_1}{0.75\textwidth}

在左侧列表中选择~Floppy~进入虚拟机软盘设置界面：\\
\FIGFIX{虚拟机软盘设置}{vb_set_2}{0.75\textwidth}

点击~Image File~最右侧的文件夹标志，进入选择软盘镜像界面：\\
\FIGFIX{选择软盘镜像}{vb_set_3}{0.75\textwidth}

点击~Add~按钮添加新的软盘镜像~emptydisk.img，并点击~select~按钮选中其作为启动软盘：\\
\FIGFIX{选择启动软盘镜像}{vb_set_4}{0.75\textwidth}

返回虚拟机软盘设置界面后，点击~OK~确认镜像文件信息：\\
\FIGFIX{确认启动镜像软盘文件信息}{vb_set_5}{0.75\textwidth}

返回虚拟机主界面，查看右侧的虚拟机设置信息：\\
\FIGFIX{查看虚拟机设置信息}{vb_main_3}{0.75\textwidth}

选中虚拟机后，双击或点击~Start~按钮运行它，第一次运行可能给出如下信息：\\
\FIGFIX{自动键盘捕获警告信息}{vb_once_4}{0.75\textwidth}

这个对话框的意思就是，当鼠标在虚拟机内部点击时，鼠标和键盘的消息将被虚拟机自动捕获，成为虚拟机的键盘和鼠标，可以敲击键盘右侧的~Ctrl~键解除捕获。

显示虚拟机的运行时内容：\\
\FIGFIX{虚拟机运行时}{vb_run_1}{0.75\textwidth}

我们可以看到在图~\ref{vb_run_1}~中，虚拟机加载空白软盘启动后提示消息为：“FATAL: No bootable medium found! System halted.”，换成中文是找不到可启动媒体，系统停机。它的实际意思就是在前面第~\ref{CHboot_boot}~节“计算机启动过程”中提到的第~\ref{bootsec-1}~步中虚拟机遍历了软驱、光驱、硬盘后没有找到可启动的媒体，所以就只好停机。因为我们在启动前已经在软驱中加载了软盘镜像，所以提示信息就表明那个软盘镜像不具有启动系统的功能，那么如何才能创建一个可启动的软盘呢，我们将在第~\ref{CHsmall}~章介绍。

